"""
定义的依赖

@File: dependencies.py
"""
from pymongo import MongoClient
from pymongo.database import Database
from config import mongo_conf, mysql_conf, oauth_conf, es_conf
from urllib import parse
import motor.motor_asyncio
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from sqlmodel import Session, create_engine
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
from sqlalchemy.orm import sessionmaker
import databases
from jose import JWTError, jwt
from exception import TokenVerifyException, RolePermissionError
from service import user_service
from schema.dao.user import UserInDb
from typing import Optional
from schema.dao.user import Role
from typing import List
from datetime import datetime
from elasticsearch import AsyncElasticsearch
from pymongo.server_api import ServerApi


AsyncIOMotorDatabase = motor.motor_asyncio.AsyncIOMotorDatabase
AsyncIOMotorCollection = motor.motor_asyncio.AsyncIOMotorCollection


mongo_server_api = ServerApi('1', strict=False)



def get_mongo_db() -> Database:
    client = MongoClient(host=mongo_conf.HOST,
                         port=mongo_conf.PORT,
                         username=mongo_conf.USERNAME,
                         password=mongo_conf.PASSWORD,
                         server_api=mongo_server_api)
    mongo_db = client[mongo_conf.DATABASE]
    return mongo_db


mongo_pwd = parse.quote(mongo_conf.PASSWORD)  #
mongo_url = f'mongodb://{mongo_conf.USERNAME}:{mongo_pwd}@{mongo_conf.HOST}:{mongo_conf.PORT}'


def get_motor_db() -> AsyncIOMotorDatabase:
    client = motor.motor_asyncio.AsyncIOMotorClient(mongo_url)
    mongo_db = client[mongo_conf.DATABASE]
    return mongo_db


def get_mongo_settings_coll(motor_db=Depends(get_motor_db)) -> AsyncIOMotorCollection:
    return motor_db['settings']


def get_mongo_user_coll(motor_db=Depends(get_motor_db)) -> AsyncIOMotorCollection:
    return motor_db['user']


def get_mongo_db_of_condition_safety() -> Database:
    client = MongoClient(host='121.37.152.140',
                         port=8635,
                         username='rwuser',
                         password='dfLUL48m@+3J',
                         server_api=mongo_server_api)
    mongo_db = client['df_car_condition_safty']
    return mongo_db


es = AsyncElasticsearch(hosts=es_conf.HOSTS,
                        timeout=es_conf.TIMEOUT,
                        **es_conf.KWARGS)


def get_es() -> AsyncElasticsearch:
    """
        获取 ES Client 对象
        """
    return es


mysql_url = f'mysql+pymysql://{mysql_conf.USERNAME}:{mysql_conf.PASSWORD}@{mysql_conf.HOST}:{mysql_conf.PORT}/{mysql_conf.DATABASE}'
aio_mysql_url = f'mysql+aiomysql://{mysql_conf.USERNAME}:{mysql_conf.PASSWORD}@{mysql_conf.HOST}:{mysql_conf.PORT}/{mysql_conf.DATABASE}'
engine = create_engine(mysql_url, echo=True)
async_engine = create_async_engine(aio_mysql_url)
SessionLocal = sessionmaker(class_=AsyncSession, bind=async_engine)


def get_mysql_session():
    with Session(engine) as session:
        yield session


async def get_mysql_async_session():
    async with SessionLocal() as session:
        yield session


mysql_db = databases.Database(aio_mysql_url)


def get_mysql_db():
    """
    获取用于异步操作的 database
    :return:
    """
    return mysql_db


oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/user/login")


async def get_current_user(token: str = Depends(oauth2_scheme),
                           coll: AsyncIOMotorCollection = Depends(get_mongo_user_coll)) -> Optional[UserInDb]:
    """
    根据 token 中的数据查找到相应的用户
    :return: token 对应的用户，如果没有找到则为空
    """
    # 首先解析出 token 中的数据
    try:
        payload = jwt.decode(token, oauth_conf.SECRET_KEY, algorithms=[oauth_conf.ALGORITHM])
        username_from_token: str = payload.get('sub')
        expire_time = payload.get('exp')
        if (username_from_token is None) or (expire_time is None):
            raise TokenVerifyException('token 信息错误')
    except JWTError:
        raise TokenVerifyException('token 解析错误')
    # 根据 username 查找出 user
    cur_user = await user_service.find_user_by_username(coll, username_from_token)
    # 判断 token 是否过期
    if (cur_user is not None and cur_user.token is None) or expire_time <= datetime.now().timestamp():
        await user_service.update_user_token(coll, username_from_token, None)
        raise TokenVerifyException('token 失效')
    return cur_user


async def _verify_roles(cur_user: Optional[UserInDb], require_roles: List[Role]) -> None:
    """
    验证角色的权限
    :param cur_user: 当前用户
    :param require_roles: 接口所要求的的角色
    :return: None，权限错误时抛出 RolePermissionError 异常
    """
    if not cur_user:
        raise RolePermissionError()
    role_of_cur_user = cur_user.role
    require_roles = {r.value for r in require_roles}
    if role_of_cur_user not in require_roles:
        raise RolePermissionError()


async def require_common(cur_user: Optional[UserInDb] = Depends(get_current_user)):
    """
    要求角色是 common 用户
    """
    await _verify_roles(cur_user, [Role.COMMON])


async def require_admin(cur_user: Optional[UserInDb] = Depends(get_current_user)):
    """
    要求角色是 admin 用户
    """
    await _verify_roles(cur_user, [Role.ADMIN])


async def require_login(cur_user: Optional[UserInDb] = Depends(get_current_user)):
    """
    要求必须登录
    """
    await _verify_roles(cur_user, [Role.COMMON, Role.ADMIN])
